package hikari.persistence;

import com.gooddies.wiring.Wiring;
import com.gooddies.wiring.annotations.Wire;
import com.gooddies.wiring.annotations.WiringComponent;
import hikari.gui.MainWindow;
import hikari.gui.panels.HierarchyTree;
import hikari.managers.Manager3D;
import hikari.managers.NavmeshManager;
import hikari.objects3d.Basic3dObject;
import hikari.persistence.file.Chunk;
import hikari.persistence.file.FileSection;
import hikari.persistence.file.HikariFile;
import hikari.persistence.file.PersistenceSection;
import hikari.utils.Utils;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.tree.DefaultMutableTreeNode;

/**
 * @author sad
 */
@WiringComponent(singleton = false, lazy = true)
public class SaveManager {

    @Wire
    private Utils utils;
    @Wire
    private Manager3D manager3D;
    private int currentIndex;
    private final HashMap<Integer, Basic3dObject> idsToObject = new HashMap<>();
    private final HashMap<Basic3dObject, Integer> objectToIds = new HashMap<>();
    @Wire
    private NavmeshManager navmeshManager;
    @Wire
    private HierarchyTree hierarchyTree;
    @Wire
    private MainWindow mainWindow;

    private SaveManager() {
    }

    private void reset() {
        currentIndex = 0;
        idsToObject.clear();
        objectToIds.clear();
    }

    private int getNextIndex() {
        currentIndex++;
        return currentIndex;
    }

    private void pushObject(Basic3dObject obj) {
        int id = getNextIndex();
        idsToObject.put(id, obj);
        objectToIds.put(obj, id);
    }

    private void pushObject(Basic3dObject obj, int id) {
        idsToObject.put(id, obj);
        objectToIds.put(obj, id);
    }

    private Basic3dObject getObjectById(int id) {
        return idsToObject.get(id);
    }

    private int getIdByObject(Basic3dObject obj) {
        if (obj == null) {
            return -1;
        }
        Integer id = objectToIds.get(obj);
        if (id == null) {
            pushObject(obj);
        }
        return objectToIds.get(obj);
    }

    public boolean saveScene(File file) {
        try {
            file.delete();
            HikariFile writer = new HikariFile(file);
            writer.openForWriting();
            reset();
            boolean result = saveNodeRecursively(writer, (DefaultMutableTreeNode) hierarchyTree.getModel().getRoot());
            if (result != false) {
                savePathFind(writer);
            }

            try {
                writer.close();
            } catch (IOException ex) {
                utils.logError(ex);
            }

            return result;
        } catch (IOException ex) {
            utils.logError(ex);
        }

        return false;
    }

    private void savePathFind(FileSection section) throws IOException {
        if (navmeshManager.getCells().isEmpty()) {
            return;
        }
        FileSection s = section.createNewSection("navmesh");
        navmeshManager.save(s);
        s.close();
    }

    public boolean saveNodeRecursively(HikariFile writer, DefaultMutableTreeNode node) {
        goThroughtNodes(node, -1, writer);
        return true;
    }

    private boolean goThroughtNodes(DefaultMutableTreeNode node, int parentId, HikariFile writer) {
        int currentId = -1;

        Object o = node.getUserObject();
        if (o instanceof Basic3dObject) {
            Basic3dObject object = (Basic3dObject) o;
            pushObject(object);
            currentId = getIdByObject(object);
            saveObject(object, parentId, currentId, writer);
        }

        int childCount = node.getChildCount();
        for (int i = 0; i < childCount; i++) {
            DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
            if (!goThroughtNodes(child, currentId, writer)) {
                return false;
            }

        }

        return true;
    }

    private boolean saveObject(Basic3dObject o, int parentId, int currentId, HikariFile writer) {
        o.write(writer, parentId, currentId);
        return true;
    }

    public boolean openScene(File file) {
        try {
            reset();
            if (!file.exists()) {
                utils.errorMessage(mainWindow, "Scene file does not exist");
                return false;
            }

            HikariFile reader = new HikariFile(file);
            try {
                reader.openForReading();
                readFile(reader);
            } finally {
                reader.close();
            }
            return true;
        } catch (Exception ex) {
            Logger.getLogger(SaveManager.class.getName()).log(Level.SEVERE, null, ex);
            utils.errorMessage(mainWindow, "Cannot open file " + file.getAbsolutePath() + " for reading.");
            return false;
        }
    }

    public boolean readFile(HikariFile reader) throws IOException {
        List<Chunk> sectionsChunk = reader.getAllSections();
        //reader.getAllAtributes();
        for (Chunk chunk : sectionsChunk) {
            switch (chunk.getName()) {
                case "3dObject":
                    PersistenceSection section = reader.readSection(chunk);
                    Object3dCreator creator = (Object3dCreator) Wiring.getComponent(Object3dCreator.class);
                    Basic3dObject object = creator.create(manager3D.getSceneManager(), section);
                    int id = creator.getCurrentId();
                    pushObject(object, id);
                    int parentId = creator.getParentId();
                    if (object != null) {
                        manager3D.AddObject(object);

                        if (parentId != -1) {
                            Basic3dObject parent = getObjectById(parentId);
                            hierarchyTree.changeParentingOnlyInTree(object, parent);
                            object.getMeshSceneNode().setParent(parent.getMeshSceneNode());
                        }
                    }
                    break;

                case "navmesh":
                    FileSection navmeshSection = reader.readSection(chunk);
                    navmeshManager.load(navmeshSection);
                    navmeshSection.close();
                    break;
            }
        }

        return true;
    }
}
